package com.simple.create.singleton.sample3;

import com.simple.LogUtil;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 单例模式3
 * 在方法内部使用synchronized关键字, 只有在创建的时候需要加锁, 提升了性能
 * 弊端:
 * 在Java的指令中, 创建对象的操作和赋值操作是分开的
 * 即: instance = new Singleton();
 * 这个语句其实需要有两步操作, 创建对象, 赋值
 * 但JVM不保证这两个操作的先后顺序
 * eg1: 分配内存空间, 先将引用指向该内存空间; 再初始化内存空间, 将该内存空间所存的实例对象的初始化数据写入;
 * eg2: 分配内存空间, 先初始化内存空间, 将该内存空间所存的实例对象的初始化数据写入; 再将引用指向该内存空间;
 * eg2是没有问题的.
 * eg1是有可能出错的:
 * 例如a,b线程同时进入com.simple.create.singleton.sample3.Singleton#getInstance()
 * a,b都判断到instance为null, 此时a拿到了LOCK, 进入synchronized方法块
 * a线程在引用指向后,线程离开了synchronized方法块(此时JVM还没有初始化实例数据)
 * b线程拿到了LOCK, 进入synchronized方法块, 判断到这个引用不是null, 也离开了synchronized方法块 直接返回了null
 * 然后b线程立即使用这个实例, 就会报NullPointerException(此时JVM还没有初始化实例数据)
 *
 * @author simple
 * @version 1.0
 * @date 2018/12/6 14:39
 * @since 1.0
 */
public class Singleton {
    private static final Object LOCK = new Object();
    private static AtomicInteger count = new AtomicInteger(0);

    /**
     * 持有私有的静态实例, 防止被引用, 赋值为null, 实现延迟加载
     */
    private static Singleton instance = null;

    /**
     * 私有的构造方法, 防止被外部实例化(反射?)
     */
    private Singleton() {

    }

    public static int getInstanceCount() {
        return count.get();
    }

    /**
     * 提供静态方法, 用于创建实例
     *
     * @return 实例
     */
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (LOCK) {
                if (null == instance) {
                    LogUtil.info("第" + count.incrementAndGet() + "个实例开始创建...");
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    /**
     * 如果该对象被序列化, 可以保证对象在序列化前后保持一致
     *
     * @return 实例
     */
    public Object readResolve() {
        return instance;
    }
}
